根據經驗法則,您永遠不應該信任從終端使用者收到的資料,並且應始終在善加利用之前驗證它。
給定一個使用使用者輸入填入的 模型,您可以透過呼叫 yii\base\Model::validate() 方法來驗證輸入。此方法將傳回布林值,指出驗證是否成功。如果沒有成功,您可以從 yii\base\Model::$errors 屬性取得錯誤訊息。例如,
$model = new \app\models\ContactForm();
// populate model attributes with user inputs
$model->load(\Yii::$app->request->post());
// which is equivalent to the following:
// $model->attributes = \Yii::$app->request->post('ContactForm');
if ($model->validate()) {
// all inputs are valid
} else {
// validation failed: $errors is an array containing error messages
$errors = $model->errors;
}
為了使 validate()
真正運作,您應該為您計劃驗證的屬性宣告驗證規則。這應該透過覆寫 yii\base\Model::rules() 方法來完成。以下範例顯示如何宣告 ContactForm
模型的驗證規則
public function rules()
{
return [
// the name, email, subject and body attributes are required
[['name', 'email', 'subject', 'body'], 'required'],
// the email attribute should be a valid email address
['email', 'email'],
];
}
rules() 方法應傳回規則陣列,每個規則都是以下格式的陣列
[
// required, specifies which attributes should be validated by this rule.
// For a single attribute, you can use the attribute name directly
// without having it in an array
['attribute1', 'attribute2', ...],
// required, specifies the type of this rule.
// It can be a class name, validator alias, or a validation method name
'validator',
// optional, specifies in which scenario(s) this rule should be applied
// if not given, it means the rule applies to all scenarios
// You may also configure the "except" option if you want to apply the rule
// to all scenarios except the listed ones
'on' => ['scenario1', 'scenario2', ...],
// optional, specifies additional configurations for the validator object
'property1' => 'value1', 'property2' => 'value2', ...
]
對於每個規則,您必須至少指定規則適用於哪些屬性以及規則的類型。您可以使用以下形式之一指定規則類型
required
、in
、date
等。請參閱 核心驗證器 以取得完整的核心驗證器列表。一個規則可用於驗證一個或多個屬性,而一個屬性可以由一個或多個規則驗證。規則可能僅在指定 on
選項的情況下,在某些 情境 中應用。如果您未指定 on
選項,則表示該規則將應用於所有情境。
當呼叫 validate()
方法時,它會執行以下步驟來執行驗證
根據上述驗證步驟,只有當屬性是 scenarios()
中宣告的活動屬性,並且與 rules()
中宣告的一個或多個活動規則相關聯時,才會驗證該屬性。
注意:為規則命名很方便,例如:
public function rules() { return [ // ... 'password' => [['password'], 'string', 'max' => 60], ]; }
您可以在子模型中使用它
public function rules() { $rules = parent::rules(); unset($rules['password']); return $rules; }
大多數驗證器都有預設的錯誤訊息,當模型的屬性驗證失敗時,這些訊息將被新增到正在驗證的模型中。例如,當 username
屬性使用此驗證器規則驗證失敗時,required 驗證器將會新增訊息「使用者名稱不能為空。」到模型中。
您可以透過在宣告規則時指定 message
屬性來自訂規則的錯誤訊息,如下所示:
public function rules()
{
return [
['username', 'required', 'message' => 'Please choose a username.'],
];
}
某些驗證器可能支援額外的錯誤訊息,以更精確地描述驗證失敗的不同原因。例如,當驗證的值太大和太小時,number 驗證器支援 tooBig 和 tooSmall 來描述驗證失敗。您可以像在驗證規則中配置驗證器的其他屬性一樣配置這些錯誤訊息。
當呼叫 yii\base\Model::validate() 時,它將呼叫兩個您可以覆寫的方法來自訂驗證過程
若要僅在特定條件適用時驗證屬性,例如,一個屬性的驗證取決於另一個屬性的值,您可以使用 when 屬性來定義此類條件。例如,
['state', 'required', 'when' => function($model) {
return $model->country == 'USA';
}]
when 屬性接受具有以下簽章的 PHP 可呼叫函式
/**
* @param Model $model the model being validated
* @param string $attribute the attribute being validated
* @return bool whether the rule should be applied
*/
function ($model, $attribute)
如果您還需要支援用戶端條件式驗證,則應配置 whenClient 屬性,該屬性接受代表 JavaScript 函式的字串,其傳回值決定是否應用該規則。例如,
['state', 'required', 'when' => function ($model) {
return $model->country == 'USA';
}, 'whenClient' => "function (attribute, value) {
return $('#country').val() == 'USA';
}"]
使用者輸入通常需要過濾或預處理。例如,您可能想要修剪 username
輸入周圍的空格。您可以使用驗證規則來實現此目標。
以下範例示範如何使用 trim 和 default 核心驗證器來修剪輸入中的空格,並將空輸入轉換為 null。
return [
[['username', 'email'], 'trim'],
[['username', 'email'], 'default'],
];
您也可以使用更通用的 filter 驗證器來執行更複雜的資料過濾。
如您所見,這些驗證規則實際上並未驗證輸入。相反地,它們將處理這些值並將它們儲存回正在驗證的屬性中。
以下範例程式碼顯示了使用者輸入的完整處理過程,這將確保只有整數值儲存在屬性中
['age', 'trim'],
['age', 'default', 'value' => null],
['age', 'integer', 'min' => 0],
['age', 'filter', 'filter' => 'intval', 'skipOnEmpty' => true],
上述程式碼將對輸入執行以下操作
null
儲存在資料庫中;我們區分「未設定」的值和實際值 0
。如果不允許 null
,您可以在此處設定另一個預設值。true
。'42'
轉換為整數 42
。在此,我們將 $skipOnEmpty 設定為 true
,這在 filter 驗證器上預設為 false
。當從 HTML 表單提交輸入資料時,如果輸入為空,您通常需要為輸入指定一些預設值。您可以使用 default 驗證器來執行此操作。例如,
return [
// set "username" and "email" as null if they are empty
[['username', 'email'], 'default'],
// set "level" to be 1 if it is empty
['level', 'default', 'value' => 1],
];
預設情況下,如果輸入的值為空字串、空陣列或 null
,則視為空輸入。您可以透過使用 PHP 可呼叫函式配置 yii\validators\Validator::isEmpty() 屬性來自訂預設的空值偵測邏輯。例如,
['agree', 'required', 'isEmpty' => function ($value) {
return empty($value);
}]
注意:如果大多數驗證器的 yii\validators\Validator::$skipOnEmpty 屬性採用預設值
true
,則它們不會處理空輸入。如果它們相關聯的屬性收到空輸入,則它們將在驗證期間被跳過。在 核心驗證器 中,只有captcha
、default
、filter
、required
和trim
驗證器會處理空輸入。
有時您需要對未繫結到任何模型的值執行臨時驗證。
如果您只需要執行一種驗證類型(例如,驗證電子郵件地址),您可以呼叫所需驗證器的 validate() 方法,如下所示
$email = 'test@example.com';
$validator = new yii\validators\EmailValidator();
if ($validator->validate($email, $error)) {
echo 'Email is valid.';
} else {
echo $error;
}
注意:並非所有驗證器都支援此類型的驗證。例如,unique 核心驗證器僅設計用於模型。
注意:yii\base\Validator::skipOnEmpty 屬性僅用於 yii\base\Model 驗證。在沒有模型的情況下使用它無效。
如果您需要對多個值執行多個驗證,則可以使用 yii\base\DynamicModel,它支援即時宣告屬性和規則。它的用法如下
public function actionSearch($name, $email)
{
$model = DynamicModel::validateData(['name' => $name, 'email' => $email], [
[['name', 'email'], 'string', 'max' => 128],
['email', 'email'],
]);
if ($model->hasErrors()) {
// validation fails
} else {
// validation succeeds
}
}
yii\base\DynamicModel::validateData() 方法建立 DynamicModel
的實例,使用給定的資料(在本範例中為 name
和 email
)定義屬性,然後使用給定的規則呼叫 yii\base\Model::validate()。
或者,您可以使用以下更「經典」的語法來執行臨時資料驗證
public function actionSearch($name, $email)
{
$model = new DynamicModel(['name' => $name, 'email' => $email]);
$model->addRule(['name', 'email'], 'string', ['max' => 128])
->addRule('email', 'email')
->validate();
if ($model->hasErrors()) {
// validation fails
} else {
// validation succeeds
}
}
驗證後,您可以透過呼叫 hasErrors() 方法來檢查驗證是否成功,然後從 errors 屬性取得驗證錯誤,就像您對一般模型所做的那樣。您也可以存取透過模型實例定義的動態屬性,例如,$model->name
和 $model->email
。
除了使用 Yii 版本中包含的 核心驗證器 之外,您還可以建立自己的驗證器。您可以建立行內驗證器或獨立驗證器。
行內驗證器是根據模型方法或匿名函式定義的驗證器。方法/函式的簽章為
/**
* @param string $attribute the attribute currently being validated
* @param mixed $params the value of the "params" given in the rule
* @param \yii\validators\InlineValidator $validator related InlineValidator instance.
* This parameter is available since version 2.0.11.
* @param mixed $current the currently validated value of attribute.
* This parameter is available since version 2.0.36.
*/
function ($attribute, $params, $validator, $current)
如果屬性驗證失敗,則方法/函式應呼叫 yii\base\Model::addError() 以將錯誤訊息儲存在模型中,以便稍後可以檢索回來呈現給終端使用者。
以下是一些範例
use yii\base\Model;
class MyForm extends Model
{
public $country;
public $token;
public function rules()
{
return [
// an inline validator defined as the model method validateCountry()
['country', 'validateCountry'],
// an inline validator defined as an anonymous function
['token', function ($attribute, $params, $validator) {
if (!ctype_alnum($this->$attribute)) {
$this->addError($attribute, 'The token must contain letters or digits.');
}
}],
];
}
public function validateCountry($attribute, $params, $validator)
{
if (!in_array($this->$attribute, ['USA', 'Indonesia'])) {
$this->addError($attribute, 'The country must be either "USA" or "Indonesia".');
}
}
}
注意:自 2.0.11 版本起,您可以改用 yii\validators\InlineValidator::addError() 來新增錯誤。這樣,錯誤訊息可以立即使用 yii\i18n\I18N::format() 格式化。在錯誤訊息中使用
{attribute}
和{value}
分別參照屬性標籤(無需手動取得)和屬性值
$validator->addError($this, $attribute, 'The value "{value}" is not acceptable for {attribute}.');
注意:預設情況下,如果行內驗證器相關聯的屬性收到空輸入,或者如果它們已經未能通過某些驗證規則,則不會應用行內驗證器。如果您想確保始終應用規則,您可以將規則宣告中的 skipOnEmpty 和/或 skipOnError 屬性配置為
false
。例如
[ ['country', 'validateCountry', 'skipOnEmpty' => false, 'skipOnError' => false], ]
獨立驗證器是擴展 yii\validators\Validator 或其子類別的類別。您可以透過覆寫 yii\validators\Validator::validateAttribute() 方法來實作其驗證邏輯。如果屬性驗證失敗,請呼叫 yii\base\Model::addError() 以將錯誤訊息儲存在模型中,就像您對 行內驗證器 所做的那樣。
例如,上面的行內驗證器可以移至新的 [[components/validators/CountryValidator]] 類別中。在這種情況下,我們可以使用 yii\validators\Validator::addError() 為模型設定自訂訊息。
namespace app\components;
use yii\validators\Validator;
class CountryValidator extends Validator
{
public function validateAttribute($model, $attribute)
{
if (!in_array($model->$attribute, ['USA', 'Indonesia'])) {
$this->addError($model, $attribute, 'The country must be either "{country1}" or "{country2}".', ['country1' => 'USA', 'country2' => 'Indonesia']);
}
}
}
如果您希望驗證器支援在沒有模型的情況下驗證值,則還應覆寫 yii\validators\Validator::validate()。您也可以覆寫 yii\validators\Validator::validateValue() 而不是 validateAttribute()
和 validate()
,因為預設情況下,後兩種方法是透過呼叫 validateValue()
實作的。
以下是如何在您的模型中使用上述驗證器類別的範例。
namespace app\models;
use Yii;
use yii\base\Model;
use app\components\validators\CountryValidator;
class EntryForm extends Model
{
public $name;
public $email;
public $country;
public function rules()
{
return [
[['name', 'email'], 'required'],
['country', CountryValidator::class],
['email', 'email'],
];
}
}
有時驗證器涉及多個屬性。請考慮以下表單
class MigrationForm extends \yii\base\Model
{
/**
* Minimal funds amount for one adult person
*/
const MIN_ADULT_FUNDS = 3000;
/**
* Minimal funds amount for one child
*/
const MIN_CHILD_FUNDS = 1500;
public $personalSalary;
public $spouseSalary;
public $childrenCount;
public $description;
public function rules()
{
return [
[['personalSalary', 'description'], 'required'],
[['personalSalary', 'spouseSalary'], 'integer', 'min' => self::MIN_ADULT_FUNDS],
['childrenCount', 'integer', 'min' => 0, 'max' => 5],
[['spouseSalary', 'childrenCount'], 'default', 'value' => 0],
['description', 'string'],
];
}
}
假設我們需要檢查家庭收入是否足夠孩子使用。我們可以為此建立行內驗證器 validateChildrenFunds
,它只會在 childrenCount
大於 0 時執行。
請注意,在附加驗證器時,我們無法使用所有已驗證的屬性 (['personalSalary', 'spouseSalary', 'childrenCount']
)。這是因為相同的驗證器將為每個屬性執行(總共 3 次),而我們只需要為整個屬性集執行一次。
您可以改用這些屬性中的任何一個(或使用您認為最相關的屬性)
['childrenCount', 'validateChildrenFunds', 'when' => function ($model) {
return $model->childrenCount > 0;
}],
validateChildrenFunds
的實作可以像這樣
public function validateChildrenFunds($attribute, $params)
{
$totalSalary = $this->personalSalary + $this->spouseSalary;
// Double the minimal adult funds if spouse salary is specified
$minAdultFunds = $this->spouseSalary ? self::MIN_ADULT_FUNDS * 2 : self::MIN_ADULT_FUNDS;
$childFunds = $totalSalary - $minAdultFunds;
if ($childFunds / $this->childrenCount < self::MIN_CHILD_FUNDS) {
$this->addError('childrenCount', 'Your salary is not enough for children.');
}
}
您可以忽略 $attribute
參數,因為驗證不只與一個屬性相關。
在多個屬性的情況下新增錯誤可能會因所需的表單設計而異
$this->addError('childrenCount', 'Your salary is not enough for children.');
addError
之前將其儲存在單獨的變數中,以保持程式碼 DRY。$message = 'Your salary is not enough for children.';
$this->addError('personalSalary', $message);
$this->addError('wifeSalary', $message);
$this->addError('childrenCount', $message);
或使用迴圈
$attributes = ['personalSalary', 'wifeSalary', 'childrenCount'];
foreach ($attributes as $attribute) {
$this->addError($attribute, 'Your salary is not enough for children.');
}
*
,因為在該點不會檢查屬性是否存在。$this->addError('*', 'Your salary is not enough for children.');
結果,我們將看不到表單欄位附近的錯誤訊息。若要顯示它,我們可以在視圖中包含錯誤摘要
<?= $form->errorSummary($model) ?>
注意:在 社群食譜 中,已詳細說明如何建立一次驗證多個屬性的驗證器。
當終端使用者透過 HTML 表單提供輸入時,基於 JavaScript 的用戶端驗證是理想的,因為它允許使用者更快地找出輸入錯誤,從而提供更好的使用者體驗。除了伺服器端驗證之外,您可以使用或實作支援用戶端驗證的驗證器。
資訊:雖然用戶端驗證是理想的,但並非必須。其主要目的是為使用者提供更好的體驗。與來自終端使用者的輸入資料類似,您永遠不應信任用戶端驗證。因此,您應始終呼叫 yii\base\Model::validate() 來執行伺服器端驗證,如前述子章節所述。
許多 核心驗證器 開箱即用支援用戶端驗證。您只需要使用 yii\widgets\ActiveForm 來建構您的 HTML 表單。例如,下面的 LoginForm
宣告了兩個規則:一個使用 required 核心驗證器,它在用戶端和伺服器端都支援;另一個使用僅在伺服器端支援的 validatePassword
行內驗證器。
namespace app\models;
use yii\base\Model;
use app\models\User;
class LoginForm extends Model
{
public $username;
public $password;
public function rules()
{
return [
// username and password are both required
[['username', 'password'], 'required'],
// password is validated by validatePassword()
['password', 'validatePassword'],
];
}
public function validatePassword()
{
$user = User::findByUsername($this->username);
if (!$user || !$user->validatePassword($this->password)) {
$this->addError('password', 'Incorrect username or password.');
}
}
}
由以下程式碼建構的 HTML 表單包含兩個輸入欄位 username
和 password
。如果您在未輸入任何內容的情況下提交表單,您會發現要求您輸入內容的錯誤訊息立即出現,而無需與伺服器進行任何通訊。
<?php $form = yii\widgets\ActiveForm::begin(); ?>
<?= $form->field($model, 'username') ?>
<?= $form->field($model, 'password')->passwordInput() ?>
<?= Html::submitButton('Login') ?>
<?php yii\widgets\ActiveForm::end(); ?>
在幕後,yii\widgets\ActiveForm 將讀取模型中宣告的驗證規則,並為支援用戶端驗證的驗證器產生適當的 JavaScript 程式碼。當使用者變更輸入欄位的值或提交表單時,將觸發用戶端驗證 JavaScript。
如果您想完全關閉用戶端驗證,您可以將 yii\widgets\ActiveForm::$enableClientValidation 屬性配置為 false
。您也可以透過將個別輸入欄位的 yii\widgets\ActiveField::$enableClientValidation 屬性配置為 false 來關閉其用戶端驗證。當 enableClientValidation
在輸入欄位層級和表單層級都配置時,前者將優先。
資訊:自 2.0.11 版本起,所有從 yii\validators\Validator 擴展的驗證器都從單獨的方法接收用戶端選項 - yii\validators\Validator::getClientOptions()。您可以使用它
- 如果您想實作自己的自訂用戶端驗證,但保留與伺服器端驗證器選項的同步;
- 進行擴展或自訂以符合您的特定需求
public function getClientOptions($model, $attribute) { $options = parent::getClientOptions($model, $attribute); // Modify $options here return $options; }
若要建立支援用戶端驗證的驗證器,您應該實作 yii\validators\Validator::clientValidateAttribute() 方法,此方法會回傳一段 JavaScript 程式碼,用於在用戶端執行驗證。在 JavaScript 程式碼中,您可以使用下列預先定義的變數
attribute
:要驗證的屬性名稱。value
:要驗證的值。messages
:一個陣列,用於保存屬性的驗證錯誤訊息。deferred
:一個陣列,可將延遲物件 (Deferred object) 推入其中 (將在下一小節中說明)。在以下範例中,我們建立一個 StatusValidator
,用於驗證輸入是否為有效的狀態輸入,並比對現有的狀態資料。此驗證器同時支援伺服器端和用戶端驗證。
namespace app\components;
use yii\validators\Validator;
use app\models\Status;
class StatusValidator extends Validator
{
public function init()
{
parent::init();
$this->message = 'Invalid status input.';
}
public function validateAttribute($model, $attribute)
{
$value = $model->$attribute;
if (!Status::find()->where(['id' => $value])->exists()) {
$model->addError($attribute, $this->message);
}
}
public function clientValidateAttribute($model, $attribute, $view)
{
$statuses = json_encode(Status::find()->select('id')->asArray()->column());
$message = json_encode($this->message, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
return <<<JS
if ($.inArray(value, $statuses) === -1) {
messages.push($message);
}
JS;
}
}
提示: 上述程式碼主要用於示範如何支援用戶端驗證。在實務上,您可以使用 in 核心驗證器來達到相同的目的。您可以像下面這樣撰寫驗證規則
[ ['status', 'in', 'range' => Status::find()->select('id')->asArray()->column()], ]
提示: 如果您需要手動處理用戶端驗證,例如動態新增欄位或進行一些自訂 UI 邏輯,請參考 Yii 2.0 Cookbook 中的 透過 JavaScript 使用 ActiveForm。
如果您需要執行非同步用戶端驗證,您可以建立 延遲物件 (Deferred object)。例如,若要執行自訂 AJAX 驗證,您可以使用以下程式碼
public function clientValidateAttribute($model, $attribute, $view)
{
return <<<JS
deferred.push($.get("/check", {value: value}).done(function(data) {
if ('' !== data) {
messages.push(data);
}
}));
JS;
}
在上述程式碼中,deferred
變數由 Yii 提供,它是一個延遲物件 (Deferred object) 的陣列。$.get()
jQuery 方法會建立一個延遲物件 (Deferred object),並將其推入 deferred
陣列中。
您也可以明確地建立一個延遲物件 (Deferred object),並在非同步回呼觸發時呼叫其 resolve()
方法。以下範例示範如何在用戶端驗證上傳圖片檔案的尺寸。
public function clientValidateAttribute($model, $attribute, $view)
{
return <<<JS
var def = $.Deferred();
var img = new Image();
img.onload = function() {
if (this.width > 150) {
messages.push('Image too wide!!');
}
def.resolve();
}
var reader = new FileReader();
reader.onloadend = function() {
img.src = reader.result;
}
reader.readAsDataURL(file);
deferred.push(def);
JS;
}
注意:
resolve()
方法必須在屬性驗證完成後呼叫。否則,主要的表單驗證將不會完成。
為了簡化操作,deferred
陣列配備了一個捷徑方法 add()
,此方法會自動建立一個延遲物件 (Deferred object) 並將其新增到 deferred
陣列中。使用此方法,您可以將上述範例簡化如下:
public function clientValidateAttribute($model, $attribute, $view)
{
return <<<JS
deferred.add(function(def) {
var img = new Image();
img.onload = function() {
if (this.width > 150) {
messages.push('Image too wide!!');
}
def.resolve();
}
var reader = new FileReader();
reader.onloadend = function() {
img.src = reader.result;
}
reader.readAsDataURL(file);
});
JS;
}
有些驗證只能在伺服器端完成,因為只有伺服器擁有必要的資訊。例如,若要驗證使用者名稱是否為唯一,則必須檢查伺服器端的使用者表格。在這種情況下,您可以使用基於 AJAX 的驗證。它會在背景觸發 AJAX 請求來驗證輸入,同時保持與常規用戶端驗證相同的使用者體驗。
若要為單一輸入欄位啟用 AJAX 驗證,請將該欄位的 enableAjaxValidation 屬性設定為 true
,並指定唯一的表單 id
use yii\widgets\ActiveForm;
$form = ActiveForm::begin([
'id' => 'registration-form',
]);
echo $form->field($model, 'username', ['enableAjaxValidation' => true]);
// ...
ActiveForm::end();
若要為表單的所有輸入啟用 AJAX 驗證,請在表單層級將 enableAjaxValidation 設定為 true
$form = ActiveForm::begin([
'id' => 'contact-form',
'enableAjaxValidation' => true,
]);
注意: 當
enableAjaxValidation
屬性同時在輸入欄位層級和表單層級設定時,前者會優先。
您還需要準備伺服器,使其可以處理 AJAX 驗證請求。這可以透過在控制器動作中使用如下所示的程式碼片段來實現
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
上述程式碼會檢查目前請求是否為 AJAX。如果是,它將透過執行驗證並以 JSON 格式回傳錯誤來回應此請求。
資訊: 您也可以使用 延遲驗證 來執行 AJAX 驗證。但是,此處描述的 AJAX 驗證功能更系統化,且需要較少的程式碼撰寫工作。
當 enableClientValidation
和 enableAjaxValidation
都設定為 true
時,AJAX 驗證請求只會在用戶端驗證成功後觸發。請注意,在驗證單一欄位的情況下 (如果 validateOnChange
、validateOnBlur
或 validateOnType
設定為 true
),只有在相關欄位單獨成功通過用戶端驗證時,才會傳送 AJAX 請求。
發現錯字或您認為此頁面需要改進嗎?
在 github 上編輯 !
您說的對,範例遺失了。在這裡
public string $text = ''; public function rules(): array { return [ ['text', 'myValidator', 'params' => ['myParam' => 1]], ]; } public function myValidator($attribute, $params, $validator, $current) { if ($params['myParam'] === 1) { $this->addError($attribute, 'Error msg'); } }
希望沒有錯字...
註冊 或 登入 以發表評論。